package com.tsyz.servlet.interceptor;

import com.tsyz.servlet.util.TraceIdUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashSet;
import java.util.Set;

/**
 * CORS跨域资源共享拦截器，用于处理跨域请求的权限控制和响应头设置
 * <p>
 * 主要功能：
 * 1. 校验请求来源是否在允许的域名列表中
 * 2. 设置必要的CORS响应头
 * 3. 处理OPTIONS预检请求
 */
@Slf4j
@Component
public class CorsInterceptor extends BaseInterceptor {

    /**
     * 内置安全域名集合，用于快速判断请求来源是否为本地开发环境或可信域名
     * 包含常见的本地开发环境域名，避免在开发时频繁修改配置
     */
    private static final Set<String> SAFE_DOMAINS = new HashSet<String>() {{
        add("localhost");       // 标准本地主机名
        add("127.0.0.1");       // IPv4回环地址
        add("localhost.com");   // 开发环境常用域名
        add("localhost.cn");    // 开发环境常用域名
    }};

    /**
     * 配置文件中定义的可跨域访问的域名列表
     * 支持通配符*表示允许所有域名
     * 支持多个域名，用逗号分隔
     * <p>
     * 示例配置：
     * cors.allowed.origins=https://example.com,https://api.example.com
     * 或
     * cors.allowed.origins=* (允许所有域名)
     */
    @Value("${tsyz.server.core.allowed.origins:*}")
    private String allowedOrigins;

    /**
     * 预处理方法，在请求处理前执行CORS相关设置
     *
     * @param request  HTTP请求对象，包含Origin等请求头
     * @param response HTTP响应对象，用于设置CORS响应头
     * @param handler  处理器对象
     * @return boolean 是否继续处理请求：true继续，false中止
     */
    @Override
    protected boolean doPreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求来源域名
        String origin = request.getHeader("Origin");

        // 安全性检查：如果启用了严格模式且来源不在允许列表中，返回403禁止访问
        if (origin != null && !isAllowedOrigin(origin)) {
            String traceId = MDC.get(TraceIdUtil.TRACE_ID);
            if (traceId != null) {
                log.warn("[traceId:{}] CORS拦截器拒绝请求，来源域名不被允许: {}", traceId, origin);
            }
            // 403 状态码
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            return false;  // 中止请求处理
        }

        // 设置CORS响应头
        // 当允许凭证时，Access-Control-Allow-Origin不能为"*"
        if (origin != null) {
            response.setHeader("Access-Control-Allow-Origin", origin);
        } else {
            response.setHeader("Access-Control-Allow-Origin", allowedOrigins.split(",")[0]);
        }


        // 允许携带凭证（如cookies）
        response.setHeader("Access-Control-Allow-Credentials", "true");
        // 允许的HTTP方法
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH");
        // 允许的请求头
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, X-XSRF-TOKEN, Accept, Origin, Range");
        // 预检请求结果缓存时间（秒）
        response.setHeader("Access-Control-Max-Age", "3600");
        // 允许客户端访问的响应头
        response.setHeader("Access-Control-Expose-Headers", "X-Total-Count, X-Pagination, Content-Disposition");

        // 处理OPTIONS预检请求 - 直接返回200状态码并不再继续处理
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            String traceId = MDC.get(TraceIdUtil.TRACE_ID);
            if (traceId != null) {
                log.debug("[traceId:{}] 处理OPTIONS预检请求，来源: {}", traceId, origin);
            }
            response.setStatus(HttpServletResponse.SC_OK);  // 200状态码
            return false;  // 不继续后续处理
        }

        return true;  // 继续后续处理
    }

    /**
     * 后处理方法，在请求处理完成后、视图渲染之前执行
     *
     * @param request      HTTP请求对象
     * @param response     HTTP响应对象
     * @param handler      处理器对象
     * @param modelAndView 模型和视图对象
     */
    @Override
    protected void doPostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // CORS处理不需要后处理
    }

    /**
     * 请求完成后的回调方法，在请求完全结束后执行
     *
     * @param request  HTTP请求对象
     * @param response HTTP响应对象
     * @param handler  处理器对象
     * @param ex       处理过程中抛出的异常
     */
    @Override
    protected void doAfterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // CORS处理不需要完成后的处理
    }

    /**
     * 校验请求来源是否在允许的域名列表中
     *
     * @param origin 请求来源域名
     * @return boolean 是否允许访问：true允许，false拒绝
     */
    private boolean isAllowedOrigin(String origin) {
        // 通配符配置，允许所有域名
        if ("*".equals(allowedOrigins)) {
            return true;
        }

        // 检查是否匹配内置安全域名（开发环境）
        for (String safeDomain : SAFE_DOMAINS) {
            // 包含匹配，支持带端口号的域名
            if (origin.contains(safeDomain)) {
                return true;
            }
        }

        // 检查是否匹配配置的允许域名
        String[] allowedOriginArray = allowedOrigins.split(",");
        for (String allowedOrigin : allowedOriginArray) {
            // 精确匹配，忽略前后空格
            if (origin.equals(allowedOrigin.trim())) {
                return true;
            }
            // 通配符匹配
            if ("*".equals(allowedOrigin.trim()) || allowedOrigin.trim().equals("")) {
                return true;
            }
        }
        // 未匹配任何允许的域名
        return false;
    }

}
